﻿/*
* edirauth - http://edirauth.codeplex.com
* A .NET library and utility for authenticating to eDirectory
* Copyright (c) 2013 Matt Shirilla (http://shirilla.com)
* Licensed under the Microsoft Public License (MS-RL)
* http://edirauth.codeplex.com/license
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Novell.Directory.Ldap;
using System.Configuration;
using System.Reflection;

namespace edirAuth
{
    public class EdirAuthorizer
    {
        string ldapServer;
        int ldapPort;
        string bindUser;
        string bindPass;
        string searchBase;
        string lookupAttrName;
        LdapConnection ldapConn = new LdapConnection();
        public string[] attrs = { "user supplied attr", "cn", "uid", "workforceId", "loginDisabled", "lockedByIntruder", "loginExpirationTime" };
        LdapSearchConstraints ldapSearchConstraints = new LdapSearchConstraints();

        public EdirAuthorizer(string ldapServer, int ldapPort, string bindUser, string bindPass, string searchBase, string lookupAttrName)  {
            this.ldapServer = ldapServer;
            this.ldapPort = ldapPort;
            this.bindUser = bindUser;
            this.bindPass = bindPass;
            this.searchBase = searchBase;
            this.lookupAttrName = lookupAttrName;
            attrs[0] = lookupAttrName;
            ldapSearchConstraints.MaxResults = 1;
        }

        public AuthInfo authenticate(string userIdentifier, string testPassword) {
            AuthInfo authInfo = new AuthInfo(userIdentifier);
            authInfo = connectAndBind(authInfo);
            if (authInfo.bindResult.Code == ResultCodes.OK) {
                authInfo = authenticate_onAttr(authInfo, testPassword);
            }
            disconnect();
            string output = String.Format("resultCode: {0}   resultDescription: {1}    uid:{2}", authInfo.passwordResult.Code, authInfo.passwordResult.Desc, userIdentifier);
            return authInfo;
        }

        AuthInfo authenticate_onAttr(AuthInfo authInfo, string testPassword)        {
            authInfo = dnLookup_onAttr(authInfo);
            if (authInfo.dnLookupResult.Code == ResultCodes.OK)
            {
                LdapAttribute passwordAttribute = new LdapAttribute("userPassword", testPassword);
                try {
                    if (ldapConn.Compare(authInfo.dnLookupResult.Desc, passwordAttribute))
                        authInfo.setPasswordResults(ResultCodes.OK, ResultCodes.OK_PASSWORD_OKAY);
                    else
                        authInfo.setPasswordResults(ResultCodes.BAD_PASS, ResultCodes.BAD_PASS_DESC);
                }
                catch (LdapException e) {
                    authInfo.setPasswordResults(e.ResultCode, e.Message);
                }
                catch (Exception e) {
                    authInfo.setPasswordResults(-1, ResultCodes.NON_LDAP_ERR_DESC + e.Message);
                }
                authInfo = checkIfLoginDisabled(authInfo);
                authInfo = checkIfIntruderLockout(authInfo);
                authInfo = checkIfExpired(authInfo);
            }

            string output = String.Format("resultCode: {0}   resultDescription: {1}    uiduid:{2}", authInfo.dnLookupResult.Code, authInfo.dnLookupResult.Desc, authInfo.attrValue);
            return authInfo;
        }

        AuthInfo checkIfLoginDisabled(AuthInfo authInfo)        {
            LdapAttribute loginDisabledAttribute = new LdapAttribute("loginDisabled", "FALSE");
            if (authInfo.ldapEntry.getAttributeSet().Contains(loginDisabledAttribute)) {
                try{
                    if (ldapConn.Compare(authInfo.ldapEntry.DN, loginDisabledAttribute)){
                        authInfo.setLoginDisabledLookupResult(ResultCodes.OK, ResultCodes.OK_LOGINDISABLED_FALSE);
                    }
                    else {
                        authInfo.setLoginDisabledLookupResult(ResultCodes.LOGINDISABLED_TRUE, ResultCodes.LOGINDISABLED_TRUE_DESC);
                    }
                }
                catch (LdapException e) {
                    authInfo.setLoginDisabledLookupResult(e.ResultCode, e.Message);
                }
                catch (Exception e) {
                    authInfo.setLoginDisabledLookupResult(-1, ResultCodes.NON_LDAP_ERR_DESC + e.Message);
                }
            }
            else
            {
                authInfo.loginDisabledResult.Code = ResultCodes.OK;
                authInfo.loginDisabledResult.Desc = ResultCodes.OK_LOGINDISABLED_NOTPRESENT;
            }
            return authInfo;
        }

        AuthInfo checkIfExpired(AuthInfo authInfo)        {
            LdapAttribute loginExpirationTime = new LdapAttribute("loginExpirationTime");
            if (authInfo.ldapEntry.getAttributeSet().Contains(loginExpirationTime)) {
                try {
                    DateTime myDate = DateTime.ParseExact(authInfo.ldapEntry.getAttribute("loginExpirationTime").StringValue, 
                                                          "yyyyMMddHHmmssZ", 
                                                          System.Globalization.CultureInfo.InvariantCulture);
                    if (DateTime.Compare(DateTime.Now, myDate) > 0) {
                        authInfo.setExpiredResult(ResultCodes.EXPIRED, ResultCodes.EXPIRED_DESC);
                    }
                    else {
                        authInfo.setExpiredResult(ResultCodes.OK, ResultCodes.OK_EXPIRED_FALSE);
                    }
                }
                catch (LdapException e) {
                    authInfo.setExpiredResult(e.ResultCode, e.Message);
                }
                catch (Exception e) {
                    authInfo.setExpiredResult(ResultCodes.NON_LDAP_ERR, ResultCodes.NON_LDAP_ERR_DESC + e.Message);
                }
            }
            else {
                authInfo.setExpiredResult(ResultCodes.OK, ResultCodes.OK_EXPIRED_NOTPRESENT);
            }
            return authInfo;
        }

        AuthInfo checkIfIntruderLockout(AuthInfo authInfo)        {
            LdapAttribute intruderLockoutAttribute = new LdapAttribute("lockedByIntruder", "FALSE");
            if (authInfo.ldapEntry.getAttributeSet().Contains(intruderLockoutAttribute))
            {
                try
                {
                    if (ldapConn.Compare(authInfo.ldapEntry.DN, intruderLockoutAttribute)){
                        authInfo.setLockedByIntruderLookupResult(ResultCodes.OK,ResultCodes.OK_INTRUDERLOCKED_FALSE);
                    }
                    else{
                        authInfo.setLockedByIntruderLookupResult(ResultCodes.INTRUDERLOCKOUT_TRUE,ResultCodes.INTRUDERLOCKOUT_TRUE_DESC);
                    }
                }
                catch (LdapException e){
                    authInfo.setLockedByIntruderLookupResult(e.ResultCode, e.Message);
                }
                catch (Exception e) {
                    authInfo.setLockedByIntruderLookupResult(-1, ResultCodes.NON_LDAP_ERR_DESC + e.Message);
                }
            }
            else
            {
                authInfo.setLockedByIntruderLookupResult(ResultCodes.OK, ResultCodes.OK_INTRUDERLOCKED_NOTPRESENT);
            }
            return authInfo;
        }

        public AuthInfo dnLookup_onAttr(AuthInfo authInfo)        {
            // note: ldapSearchResults.Count does not seems to work for me so I added the count variable.
            string filter = lookupAttrName + "=" + authInfo.attrValue;
            int count = 0;
            try
            {
                LdapEntry nextEntry = null;
                LdapSearchResults ldapSearchResults = ldapConn.Search(searchBase, LdapConnection.SCOPE_SUB, filter, attrs, false, ldapSearchConstraints);
                while (ldapSearchResults.hasMore()) {
                    count++;
                    nextEntry = ldapSearchResults.next();
                    authInfo.dnLookupResult.Code = 0;
                    authInfo.dnLookupResult.Desc = nextEntry.DN;
                    authInfo.ldapEntry = nextEntry;
                }
                if (count == 0) {
                    authInfo.dnLookupResult.Code = ResultCodes.NO_RESULTS;
                    authInfo.dnLookupResult.Desc = ResultCodes.NO_RESULTS_DESC;
                }
            }
            catch (LdapException e) {
                authInfo.setDnlookupResults(e.ResultCode,e.Message);
            }
            catch (Exception e) {
                authInfo.setDnlookupResults(ResultCodes.NON_LDAP_ERR, ResultCodes.NON_LDAP_ERR_DESC + e.Message);
            }
            string output = String.Format("resultCode: {0}   resultDescription: {1}    uiduid:{2}", authInfo.dnLookupResult.Code, authInfo.dnLookupResult.Desc, authInfo.attrValue);
            return authInfo;
        }

        public AuthInfo connectAndBind(AuthInfo authInfo)        {
            ldapConn = new LdapConnection();
            if (ldapPort == 636)
                ldapConn.SecureSocketLayer = true;
            try {
                ldapConn.Connect(ldapServer, ldapPort);
                ldapConn.Bind(LdapConnection.Ldap_V3, bindUser, bindPass);
                authInfo.setBindResults(ResultCodes.OK, ResultCodes.OK_DESC);
            }
            catch (LdapException e) {
                authInfo.setBindResults(e.ResultCode,e.Message);
            }
            catch (Exception e) {
                authInfo.setBindResults(ResultCodes.NON_LDAP_ERR, ResultCodes.NON_LDAP_ERR_DESC + e.Message);
            }
            string output = String.Format("resultCode: {0}   resultDescription: {1}", authInfo.bindResult.Code, authInfo.bindResult.Desc);
            return authInfo;
        }

        void disconnect()        {
            ldapConn.Disconnect();
            string output = String.Format("disconnected from: {0}", ldapServer);
        }


        public override string ToString()        {
            return String.Format("finished {0}; {1}; {2}; {3}; {4}", ldapServer, ldapPort, bindUser, searchBase, lookupAttrName);
        }

        #region attributes
        public string BindUser
        {
            get { return bindUser; }
            set { bindUser = value; }
        }

        public string BindPass
        {
            get { return bindPass; }
            set { bindPass = value; }
        }

        public string LdapServer
        {
            get { return ldapServer; }
            set { ldapServer = value; }
        }

        public int LdapPort
        {
            get { return ldapPort; }
            set { ldapPort = value; }
        }

        #endregion


    }//class


}//namespace
